package com.gitee.wsl.android.file

//package com.newki.choosefile.provider;

import android.content.ContentProvider
import android.content.ContentValues
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.ProviderInfo
import android.database.Cursor
import android.database.MatrixCursor
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.ParcelFileDescriptor
import android.provider.OpenableColumns
import android.text.TextUtils
import android.webkit.MimeTypeMap
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException


/**
 * 设置自定义的Provider,兼容7.1 Uri系统
 *       <provider
 *           android:name=".provider.ChooseFileProvider"
 *           android:authorities="com.newki.choosefile.file.path.share"
 *           android:exported="false"
 *           android:grantUriPermissions="true">
 *           <meta-data
 *               android:name="android.support.FILE_PROVIDER_PATHS"
 *               android:resource="@xml/choose_file_paths" />
 *       </provider>

 */
class ChooseFileProvider : ContentProvider() {

    private var mStrategy: PathStrategy? = null

    override fun onCreate(): Boolean {
        return true
    }

    override fun attachInfo(context: Context, info: ProviderInfo) {
        super.attachInfo(context, info)
        if (info.exported) {
            throw SecurityException("Provider must not be exported")
        }
        if (!info.grantUriPermissions) {
            throw SecurityException("Provider must grant uri permissions")
        }
        mStrategy = getPathStrategy(context, info.authority)
    }

    override fun query(
        uri: Uri,
        projection1: Array<String>?,
        selection: String?,
        selectionArgs: Array<String>?,
        sortOrder: String?
    ): Cursor {
        var projection = projection1
        val file = mStrategy!!.getFileForUri(uri)
        if (projection == null) {
            projection = COLUMNS
        }
        var cols = arrayOfNulls<String>(projection.size)
        var values = arrayOfNulls<Any>(projection.size)
        var i = 0
        for (col in projection) {
            if (OpenableColumns.DISPLAY_NAME == col) {
                cols[i] = OpenableColumns.DISPLAY_NAME
                values[i++] = file.name
            } else if (OpenableColumns.SIZE == col) {
                cols[i] = OpenableColumns.SIZE
                values[i++] = file.length()
            }
        }
        cols = copyOf(cols, i)
        values = copyOf(values, i)
        val cursor = MatrixCursor(cols, 1)
        cursor.addRow(values)
        return cursor
    }

    override fun getType(uri: Uri): String {
        val file = mStrategy!!.getFileForUri(uri)
        val lastDot = file.name.lastIndexOf('.')
        if (lastDot >= 0) {
            val extension = file.name.substring(lastDot + 1)
            val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
            if (mime != null) {
                return mime
            }
        }
        return "application/octet-stream"
    }

    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        throw UnsupportedOperationException("No external inserts")
    }

    override fun update(
        uri: Uri,
        values: ContentValues?,
        selection: String?,
        selectionArgs: Array<String>?
    ): Int {
        throw UnsupportedOperationException("No external updates")
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
        val file = mStrategy!!.getFileForUri(uri)
        return if (file.delete()) 1 else 0
    }

    @Throws(FileNotFoundException::class)
    override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
        val file = mStrategy!!.getFileForUri(uri)
        val fileMode = modeToMode(mode)
        return ParcelFileDescriptor.open(file, fileMode)
    }

    companion object {
        private val COLUMNS = arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)
        private const val META_DATA_FILE_PROVIDER_PATHS = "android.support.FILE_PROVIDER_PATHS"
        private const val TAG_ROOT_PATH = "root-path"
        private const val TAG_FILES_PATH = "files-path"
        private const val TAG_CACHE_PATH = "cache-path"
        private const val TAG_EXTERNAL = "external-path"
        private const val TAG_EXTERNAL_FILES = "external-files-path"
        private const val TAG_EXTERNAL_CACHE = "external-cache-path"
        private const val TAG_EXTERNAL_MEDIA = "external-media-path"
        private const val ATTR_NAME = "name"
        private const val ATTR_PATH = "path"
        private val DEVICE_ROOT = File("/")
        private val sCache = HashMap<String, PathStrategy?>()

        fun getUriForFile(context: Context, authority: String, file: File): Uri {
            val strategy = getPathStrategy(context, authority)
            return strategy!!.getUriForFile(file)
        }

        private fun getPathStrategy(context: Context, authority: String): PathStrategy? {
            var strategy: PathStrategy?
            synchronized(sCache) {
                strategy = sCache[authority]
                if (strategy == null) {
                    strategy = try {
                        parsePathStrategy(context, authority)
                    } catch (e: IOException) {
                        throw IllegalArgumentException(
                            "Failed to parse $META_DATA_FILE_PROVIDER_PATHS meta-data",
                            e
                        )
                    } catch (e: XmlPullParserException) {
                        throw IllegalArgumentException(
                            "Failed to parse $META_DATA_FILE_PROVIDER_PATHS meta-data",
                            e
                        )
                    }
                    sCache[authority] = strategy
                }
            }
            return strategy
        }

        @Throws(IOException::class, XmlPullParserException::class)
        private fun parsePathStrategy(context: Context, authority: String): PathStrategy {
            val strategy = SimplePathStrategy(authority)
            val info = context.packageManager.resolveContentProvider(authority, PackageManager.GET_META_DATA)
            val `in` = info!!.loadXmlMetaData(context.packageManager, META_DATA_FILE_PROVIDER_PATHS)
                ?: throw IllegalArgumentException("Missing $META_DATA_FILE_PROVIDER_PATHS meta-data")
            var type: Int
            while (`in`.next().also { type = it } != XmlPullParser.END_DOCUMENT) {
                if (type == XmlPullParser.START_TAG) {
                    val tag = `in`.name
                    val name = `in`.getAttributeValue(null, ATTR_NAME)
                    val path = `in`.getAttributeValue(null, ATTR_PATH)
                    var target: File? = null
                    if (TAG_ROOT_PATH == tag) {
                        target = DEVICE_ROOT
                    } else if (TAG_FILES_PATH == tag) {
                        target = context.filesDir
                    } else if (TAG_CACHE_PATH == tag) {
                        target = context.cacheDir
                    } else if (TAG_EXTERNAL == tag) {
                        target = Environment.getExternalStorageDirectory()
                    } else if (TAG_EXTERNAL_FILES == tag) {
                        val externalFilesDirs = getExternalFilesDirs(context, null)
                        if (externalFilesDirs.isNotEmpty()) {
                            target = externalFilesDirs[0]
                        }
                    } else if (TAG_EXTERNAL_CACHE == tag) {
                        val externalCacheDirs = getExternalCacheDirs(context)
                        if (externalCacheDirs.isNotEmpty()) {
                            target = externalCacheDirs[0]
                        }
                    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && TAG_EXTERNAL_MEDIA == tag) {
                        val externalMediaDirs = context.externalMediaDirs
                        if (externalMediaDirs.isNotEmpty()) {
                            target = externalMediaDirs[0]
                        }
                    }
                    if (target != null) {
                        strategy.addRoot(name, buildPath(target, path))
                    }
                }
            }
            return strategy
        }

        private fun modeToMode(mode: String): Int {
            val modeBits: Int = when (mode) {
                "r" -> {
                    ParcelFileDescriptor.MODE_READ_ONLY
                }
                "w", "wt" -> {
                    ParcelFileDescriptor.MODE_WRITE_ONLY or ParcelFileDescriptor.MODE_CREATE or ParcelFileDescriptor.MODE_TRUNCATE
                }
                "wa" -> {
                    ParcelFileDescriptor.MODE_WRITE_ONLY or ParcelFileDescriptor.MODE_CREATE or ParcelFileDescriptor.MODE_APPEND
                }
                "rw" -> {
                    ParcelFileDescriptor.MODE_READ_WRITE or ParcelFileDescriptor.MODE_CREATE
                }
                "rwt" -> {
                    ParcelFileDescriptor.MODE_READ_WRITE or ParcelFileDescriptor.MODE_CREATE or ParcelFileDescriptor.MODE_TRUNCATE
                }
                else -> {
                    throw IllegalArgumentException("Invalid mode: $mode")
                }
            }
            return modeBits
        }

        private fun buildPath(base: File, vararg segments: String): File {
            var cur = base
            for (segment in segments) {
                cur = File(cur, segment)
            }
            return cur
        }

        private fun copyOf(original: Array<String?>, newLength: Int): Array<String?> {
            val result = arrayOfNulls<String>(newLength)
            System.arraycopy(original, 0, result, 0, newLength)
            return result
        }

        private fun copyOf(original: Array<Any?>, newLength: Int): Array<Any?> {
            val result = arrayOfNulls<Any>(newLength)
            System.arraycopy(original, 0, result, 0, newLength)
            return result
        }

        private fun getExternalFilesDirs(context: Context, type: String?): Array<File?> {
            return if (Build.VERSION.SDK_INT >= 19) {
                context.getExternalFilesDirs(type)
            } else {
                arrayOf(context.getExternalFilesDir(type))
            }
        }

        fun getExternalCacheDirs(context: Context): Array<File?> {
            return if (Build.VERSION.SDK_INT >= 19) {
                context.externalCacheDirs
            } else {
                arrayOf(context.externalCacheDir)
            }
        }
    }
}

interface PathStrategy {
    fun getUriForFile(file: File): Uri
    fun getFileForUri(uri: Uri): File
}

 class SimplePathStrategy(private val mAuthority: String) : PathStrategy {
    private val mRoots = HashMap<String, File>()
    fun addRoot(name: String, root1: File) {
        var root = root1
        require(!TextUtils.isEmpty(name)) { "Name must not be empty" }
        root = try {
            root.canonicalFile
        } catch (e: IOException) {
            throw IllegalArgumentException("Failed to resolve canonical path for $root", e)
        }
        mRoots[name] = root
    }

    override fun getUriForFile(file: File): Uri {
        var path: String = try {
            file.canonicalPath
        } catch (e: IOException) {
            throw IllegalArgumentException("Failed to resolve canonical path for $file")
        }
        var mostSpecific: Map.Entry<String, File>? = null
        for (root in mRoots.entries) {
            val rootPath = root.value.path
            val invalidMost = mostSpecific == null || rootPath.length > mostSpecific.value.path.length
            if (path.startsWith(rootPath) && invalidMost) {
                mostSpecific = root
            }
        }
        requireNotNull(mostSpecific) { "Failed to find configured root that contains $path" }
        val rootPath = mostSpecific.value.path
        path = if (rootPath.endsWith("/")) {
            path.substring(rootPath.length)
        } else {
            path.substring(rootPath.length + 1)
        }
        path = Uri.encode(mostSpecific.key) + '/' + Uri.encode(path, "/")
        return Uri.Builder().scheme("content").authority(mAuthority).encodedPath(path).build()
    }

    override fun getFileForUri(uri: Uri): File {
        var path = uri.encodedPath
        val splitIndex = path!!.indexOf('/', 1)
        val tag = Uri.decode(path.substring(1, splitIndex))
        path = Uri.decode(path.substring(splitIndex + 1))
        val root = mRoots[tag] ?: throw IllegalArgumentException("Unable to find configured root for $uri")
        var file = File(root, path)
        file = try {
            file.canonicalFile
        } catch (e: IOException) {
            throw IllegalArgumentException("Failed to resolve canonical path for $file")
        }
        if (!file.path.startsWith(root.path)) {
            throw SecurityException("Resolved path jumped beyond configured root")
        }
        return file
    }
}